/*----------------------------------------------------------------------
 * SmartFilter Component
 * $Id: Base64.java,v 1.2 2009-07-14 19:39:04 abaker Exp $
 * Copyright (c) 2002 Secure Computing Corporation
 *----------------------------------------------------------------------
 *
 *
 */

package com.mobzilla.util;


public class Base64
{
    private Base64()
    {
    }

    public static String encode(final byte[] bytes)
    {
        final int two  = 2;
        final int four = 4;
        final int six  = 6;
        final int byte255 = 0xff;
        final int byte3   = 0x3;
        final int byte15  = 0xf;
        final int byte63  = 0x3f;

        if (bytes == null)
        {
            // This has to be a runtime exception, but is essentially a
            // programmer error
            throw new IllegalArgumentException("cannot encode [null] bytes.");
        }

        final int byteArrayLength = bytes.length;
        if (byteArrayLength == 0)
        {
            return "";
        }

        StringBuffer encodedString = new StringBuffer();

        int i = 0;
        int pad = 0;

        while (i < byteArrayLength)
        {
            // Get three bytes
            // Two exceptions:
            // second byte does not exist,
            // second byte and third byte do not exist.
            byte a1;
            byte a2;
            byte a3;

            a1 = bytes[i];
            i++;
            if (i >= byteArrayLength)
            {
                // Second and third bytes do not exist
                a2 = 0;
                a3 = 0;
                pad = 2;
            }
            else
            {
                a2 = bytes[i];
                i++;

                if (i >= byteArrayLength)
                {
                    // Third byte does not exist
                    a3 = 0;
                    pad = 1;
                }
                else
                {
                    // All three bytes exist
                    a3 = bytes[i];
                    i++;
                    pad = 0;
                }
            }

            // convert byte to positive integer (not the default conversion)
            int b1 = a1 & byte255;
            int b2 = a2 & byte255;
            int b3 = a3 & byte255;

            // Calculate four bytes
            // c1 = 0 0 b10 b11 b12 b13 b14 b15
            byte c1 = (byte) (b1 >> two);
            // b1 & 0x3 = 0 0 0 0 0 0 b16 b17
            // (b1 & 0x3) << 4) = 0 0 b16 b17 0 0 0 0
            // b2 >> 4 = 0 0 0 0 b20 b21 b22 b23
            // c2 = 0 0 b16 b17 b20 b21 b22 b23
            byte c2 = (byte) (((b1 & byte3) << four) | (b2 >> four));
            // b2 & 0xf = 0 0 0 0 b24 b25 b26 b27
            // (b2 & 0xf) << 2 = 0 0 b24 b25 b26 b27 0 0
            // b3 >> 6 = 0 0 0 0 0 0 b30 b31
            // c3 = 0 0 b24 b25 b26 b27 b30 b31
            byte c3 = (byte) (((b2 & byte15) << two) | (b3 >> six));
            // c4 = 0 0 b32 b33 b34 b35 b36 b37
            byte c4 = (byte) (b3 & byte63);

            encodedString.append(BASE64ARRAY[c1]);
            encodedString.append(BASE64ARRAY[c2]);

            switch (pad)
            {
                case 0:
                    encodedString.append(BASE64ARRAY[c3]);
                    encodedString.append(BASE64ARRAY[c4]);
                    break;
                case 1:
                    // No second byte, c4 = 0.
                    // Use character '=' instead.
                    encodedString.append(BASE64ARRAY[c3]);
                    encodedString.append("=");
                    break;
                case 2:
                    // No second and third bytes, c3 = 0 and c4 = 0.
                    // Use character '=' instead.
                    encodedString.append("==");
                    break;
                default:
                    break;
            }
        }

        return encodedString.toString();
    }


    /**
     * @param input  The encoded text.
     * @return       The byte array.
     */
    public static byte[] decode(final String input)
    {
        final int two   = 2;
        final int three = 3;
        final int four  = 4;
        final int six   = 6;
        String encodedText = input;

        if (encodedText == null)
        {
            throw new IllegalArgumentException("Input 64 text cannot be null");
        }
        if (encodedText.equals(""))
        {
            return new byte[0];
        }

        if (encodedText.length() % four != 0)
        {
            throw new IllegalArgumentException(
                "Encoded text length must be evenly divisible by 4, " +
                encodedText.length() +
                " is not.");
        }

        int byteArrayLength = three * (encodedText.length() / four);
        int n = encodedText.length();
        for (int i = 0; i < encodedText.length(); i++)
        {
            if (encodedText.charAt(encodedText.length() - 1 - i) != '=')
            {
                n = encodedText.length() - i;
                break;
            }
        }

        byte[] bytes = null;
        if (n == (encodedText.length() - 1))
        {
            byteArrayLength = byteArrayLength - 1;

            bytes = new byte[byteArrayLength];
            encodedText = encodedText.substring(0, n);
        }
        else if (n == (encodedText.length() - 2))
        {
            byteArrayLength = byteArrayLength - 2;
            bytes = new byte[byteArrayLength];
            encodedText = encodedText.substring(0, n);
        }
        else if (n < (encodedText.length() - 2))
        {
            throw new IllegalArgumentException("There are more than two " +
                "characters at the end equaling '='");
        }
        else
        {
            bytes = new byte[byteArrayLength];
        }

        byte c1;
        byte c2;
        byte c3 = 0;
        byte c4 = 0;
        byte b1;
        byte b2;
        byte b3;
        int i = 0;
        int caseNumber = 0;
        int byteArrayCounter = 0;
        while (i < encodedText.length())
        {
            c1 = getIndexFromBase64Array(encodedText.charAt(i));
            i++;
            if (i >= encodedText.length())
            {
                throw new IllegalArgumentException(
                    "The remainder of the length divided by 4 can not be 1");
            }
            else
            {
                c2 = getIndexFromBase64Array(encodedText.charAt(i));
                i++;
                if (i >= encodedText.length())
                {
                    caseNumber = 2;
                }
                else
                {
                   c3 = getIndexFromBase64Array(encodedText.charAt(i));
                   i++;
                   if (i >= encodedText.length())
                   {
                       caseNumber = 1;
                   }
                   else
                   {
                       c4 = getIndexFromBase64Array(encodedText.charAt(i));
                       i++;
                       caseNumber = 0;
                   }
                }
            }

            try
            {
                switch (caseNumber)
                {
                case 0:
                    b1 = (byte) ((c1 << two) | (c2 >> four));
                    bytes[byteArrayCounter] = b1;
                    byteArrayCounter++;
                    b2 = (byte) ((c2 << four) | (c3 >> two));
                    bytes[byteArrayCounter] = b2;
                    byteArrayCounter++;
                    b3 = (byte) ((c3 << six) | c4);
                    bytes[byteArrayCounter] = b3;
                    byteArrayCounter++;
                    break;
                case 1:
                    b1 = (byte) ((c1 << two) | (c2 >> four));
                    bytes[byteArrayCounter] = b1;
                    byteArrayCounter++;
                    b2 = (byte) ((c2 << four) | (c3 >> two));
                    bytes[byteArrayCounter] = b2;
                    byteArrayCounter++;
                    break;
                case 2:
                    b1 = (byte) ((c1 << two) | (c2 >> four));
                    bytes[byteArrayCounter] = b1;
                    byteArrayCounter++;
                    break;
                default:
                    break;
                }
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
                throw new IllegalArgumentException(e.getMessage());
            }
        }

        if (bytes == null)
        {
            throw new IllegalArgumentException("Byte array is null");
        }

        return bytes;
    }

    // ========================================================================
    // private static methods
    // ========================================================================

    private static byte getIndexFromBase64Array(final char base64Char)
    {
        for (int i = 0; i < BASE64ARRAY.length; i++)
        {
            if (base64Char == BASE64ARRAY[i])
            {
                return (byte) i;
            }
        }

        throw new IllegalArgumentException("The character '" +
                                           base64Char +
                                           "' is not in base 64 array");
    }

    // A-Z, a-z, 0-9, +, /
    private static final  char[] BASE64ARRAY =
    {
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
      'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
      'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
      'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
      'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
      'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
      'w', 'x', 'y', 'z', '0', '1', '2', '3',
      '4', '5', '6', '7', '8', '9', '+', '/'
   };


}
